#ifndef __GEOMETRY_DATA_H__
#define __GEOMETRY_DATA_H__

#pragma once

#include "VertexBuffer.h"
#include "VertexDeclaration.h"
#include "IndexBuffer.h"

namespace Nezha
{

	enum
	{
		// FIXME this depends on the platform
		NEZHA_MAX_VERTEX_STREAMS = 8
	};

	enum GeometryType
	{
		POLY_POINT,
		POLY_LINE_SEG,
		POLY_LINE_OPEN,
		POLY_LINE_CLOSED,
		TRIANGLE_LIST,				// default
		TRIANGLE_STRIP,
		TRIANGLE_FAN,
	};

	/** Just data buffer binding. */
	class GRAPHICS_ENTRY GeometryData : public Refable
	{
	public:
		GeometryData();
		GeometryData(GeometryType gt);
		virtual ~GeometryData();

		GeometryType getGeometryType() const
		{
			return mGeometryType;
		}

		void setVertexDeclaration(const VertexDeclaration& vd)
		{
			mVertexDecl = vd;
		}

		const VertexDeclaration& getVertexDecl() const
		{
			return mVertexDecl;
		}

		void setVertexStream(u32 index, VertexBufferPtr& vb);
		
		const VertexBufferPtr& getVertexBuffer(u32 index) const;

		void setIndexBuffer(IndexBufferPtr& ib)
		{
			mIndexBufferPtr = ib;
		}

		const IndexBufferPtr& getIndexBuffer() const
		{
			return mIndexBufferPtr;
		}

		bool hasIndexBuffer() const
		{
			return !mIndexBufferPtr.isNull();
		}

		u32 getNumVertex() const;

		u32 getNumIndices() const
		{
			return mIndexBufferPtr.ptr() ? mIndexBufferPtr->getNumIndices() : 0;		
		}

NEZHA_INTERNAL:
		void _setDataType(GeometryType gt)
		{
			mGeometryType = gt;
		}

	protected:
		IndexBufferPtr mIndexBufferPtr;
		VertexBufferPtr mVertexBufferPtrs[NEZHA_MAX_VERTEX_STREAMS];
		VertexDeclaration mVertexDecl;

		GeometryType mGeometryType;
	};

	typedef RefCountedPtr<GeometryData>::Default GeometryDataPtr;

	/** submit this data structure for single rendering.  */
	class GRAPHICS_ENTRY GeometryDataBatch
	{
	public:
		GeometryDataBatch();
		virtual ~GeometryDataBatch(){}

		// number of vertexs before start position.
		u32 getNumVertexsOffset() const
		{
			return mNumVertOffset;
		}

		// number of indices before start position.
		u32 getNumIndicesOffset() const
		{
			return mNumIdxOffset;
		}

		u32 getNumVerts() const
		{
			return mNumVerts;
		}

		u32 getNumIndices() const
		{
			return mNumIdxs;
		}

		const GeometryData* getData() const
		{
			return mGeomDataBuffer.ptr();
		}

		bool shareWith(const GeometryDataBatch& gdb, u32 numVertsOffset, u32 numIdxOffset, u32 numVerts, u32 numIdxs)
		{
			return createBatch(numVertsOffset, numIdxOffset, numVerts, numIdxs, gdb.mGeomDataBuffer);
		}

		bool createBatch(u32 numVertsOffset, u32 numIdxOffset, u32 numVerts, u32 numIdxs, const GeometryDataPtr& data);

		GeometryDataBatch& operator = (const GeometryDataBatch& gdb);

		void clear();

	protected:
		u32 mNumVertOffset;
		u32 mNumIdxOffset;
		u32 mNumVerts;
		u32 mNumIdxs;

		GeometryDataPtr mGeomDataBuffer;
	};

	struct _SupportVertexDataCat {};
	struct _UnsupportVertexDataCat {};

	template <typename T>
	class VertexDataCat
	{
	public:
		typedef _UnsupportVertexDataCat cat_tag;
	};

	template <>
	class VertexDataCat<float>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};

	template <>
	class VertexDataCat<int>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};

	template <>
	class VertexDataCat<u32>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};

	template <>
	class VertexDataCat<Vector2>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};

	template <>
	class VertexDataCat<Vector3>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};

	template <>
	class VertexDataCat<Vector4>
	{
	public:
		typedef _SupportVertexDataCat cat_tag;
		struct _Type{};
	};


	/** hleper for vertex buffer access. */
	class GRAPHICS_ENTRY VertexBufferAccessor
	{
	public:
		VertexBufferAccessor();
		virtual ~VertexBufferAccessor();

		/**
		@REMARKS
			beginAccess() & endAccess() must be dispatched in couples
		*/
		bool beginAccess(GeometryDataPtr& gd);
		void endAccess();

		bool accessProcessing() const
		{
			return mAccessProcessing;
		}

		struct VertexElemBinding
		{
			u16 Semantic;
			u16 StreamIndex;
			u32 BufferStride;					// stride of specify buffer
			float* Data;

			VertexElemBinding()
				:Semantic(0)
				,StreamIndex(0)
				,BufferStride(0)
				,Data(NULL)
			{
			}
		};

		const VertexElemBinding* getElemBinding(u32 index) const
		{
			return &mBindings[index];
		}

		const VertexElemBinding* getElemBindingByVS(VertexSementic vs, u32 index = 0) const;

		template <typename T>
		T* accessChannelPtr(const VertexElemBinding& binding, u32 unitIndex)
		{
			return _raw_castChannelPtr<T>(binding, unitIndex, VertexDataCat<T>::cat_tag());
		}

		template <typename T>
		T& accessChannel(const VertexElemBinding& binding, u32 unitIndex)
		{
			return _raw_castChannel<T>(binding, unitIndex, VertexDataCat<T>::cat_tag());
		}

		int* accessIndices()
		{
			return mIndices;
		}

		float* _accessVertexs()
		{
			return getElemBinding(0)->Data;
		}

	private:
		// do not call this function directly.
		template <typename T>
		T& _raw_castChannel(const VertexElemBinding& binding, u32 unitIndex, _SupportVertexDataCat)
		{
			return *((T*)(binding.Data + unitIndex * binding.BufferStride));
		}

		template <typename T>
		T* _raw_castChannelPtr(const VertexElemBinding& binding, u32 unitIndex, _SupportVertexDataCat)
		{
			return (T*)(binding.Data + unitIndex * binding.BufferStride);
		}

	private:
		PArray<VertexElemBinding> mBindings;
		GeometryDataPtr mBindingGD;
		int* mIndices;

		bool mAccessProcessing;
	};

}//end namespace Nezha

#endif //end __GEOMETRY_DATA_H__